#include "getProcessor.h"
#include <sstream>
#include <unistd.h>
#include <sys/stat.h>
#include <ctime>
#include <sys/sendfile.h>
#include <exception>

getProcessor::getProcessor() {
    this->method = llhttp_method::HTTP_GET;
    this->KeepAlive = false;
}

getProcessor::~getProcessor() = default;

void getProcessor::setClientSockfd(int new_fd) {
    this->ClientSockfd = new_fd;
}

llhttp_method getProcessor::getMethod() {
    return this->method;
}

/*
    HTTP中的URL知识点复习
        虚拟目录部分：从域名后的第一个“/”开始到最后一个“/”为止，是虚拟目录部分。虚拟目录也不是一个URL必须的部分。

        文件名部分：从域名后的最后一个“/”开始到“？”为止，是文件名部分，如果没有“?”,则是从域名后的最后一个“/”开始到“#”为止，
    是文件部分，如果没有“？”和“#”，那么从域名后的最后一个“/”开始到结束，都是文件名部分。
        锚部分：从“#”开始到最后，都是锚部分。

        参数部分：从“？”开始到“#”为止之间的部分为参数部分，又称搜索部分、查询部分。
    如“boardID=5&ID=24618&page=1”。参数可以允许有多个参数，参数与参数之间用“&”作为分隔符。
*/
void getProcessor::setMessageBody(const std::string& body) {
    this->messageBody["file_name"] = ""; //文件名或url中的api方法只能有一个非空
    this->messageBody["api"] = "";
    //找到'?'位置界定非参部分
    size_t index1 = 0;//记录问号的位置
    size_t index2 = 0;//记录点的位置

    while (index1 < body.size()) {
        if (body[index1] == '?') {
            break;
        } else if (body[index1] == '.') {
            index2 = index1;
        }
        index1++;
    }

    if (index2 == 0 || index2 == index1 - 1) {
        //说明不是请求静态文件 即没有准确的文件拓展名
        this->messageBody["api"] = body.substr(0, index1);
    } else {
        // 获取静态文件名称 及其拓展名
        this->messageBody["file_name"] = body.substr(0, index1);
        this->messageBody["file_type"] = body.substr(index2, index1 - index2);
    }

    if (index1 == body.size()) {
        //文件名部分也不是一个URL必须的部分，如果省略该部分，则使用默认的文件名
        return;
    }

    // 分割请求参数部分 例如 boardID=5&ID=24618&page=1
    // 注意: [ # ] 是用来指导浏览器动作的，对服务器端完全无用。所以，HTTP 请求中不包括 #。
    std::string paraStr = body.substr(index1 + 1);
    std::stringstream paraStrStream(paraStr);
    std::string aPara("");
    while (getline(paraStrStream, aPara, '&')) {
        auto equalSignPos = aPara.find('=');
        if (equalSignPos == std::string::npos) {
            // 找不到等号 参数错误
            throw std::runtime_error("[GET] 参数部分存在问题");
        }

        Json::Value param;
        param["field"] = aPara.substr(0, equalSignPos);
        param["value"] = aPara.substr(equalSignPos + 1);
        this->messageBody.append(param);
    }
}

/*
    处理请求文件
    注意：stat函数用来获取指定路径的文件或者文件夹的信息，配合结构体使用
    struct stat {
            _dev_t     st_dev;        //文件所在磁盘驱动器号
            _ino_t     st_ino;        //inode，FAT、NTFS文件系统无意义
            unsigned short st_mode;   //文件、文件夹的标志
            short      st_nlink;      //非NTFS系统上通常为1
            short      st_uid;        //UNIX系统上为userid，windows上为0
            short      st_gid;        //UNIX系统上为groupid，windows上为0
            _dev_t     st_rdev;       //驱动器号，与st_dev相同
            _off_t     st_size;       //文件字节数
            time_t st_atime;          //上次访问时间
            time_t st_mtime;          //上次修改时间
            time_t st_ctime;          //创建时间
            };

*/
void getProcessor::sendFile(const std::string& fileName) {
    std::string filePath = DIR_PATH + fileName;
    struct stat st{};
    int f; // 文件
    std::string responseHead("");

    // 发送文件,TCP_CORK选项禁用Nagle,
    // Nagle算法：Nagle 算法（以其发明者 John Nagle 命名）试图在发送一个分组之前，将大量 TCP 数据绑定在一起，以提高网路效率
    // int on = 1;
    // setsockopt(client_sockfd, SOL_TCP, TCP_CORK, &on, sizeof(on));

    if (stat(filePath.c_str(), &st) < 0) {
        //文件不存在
        if ((write(this->ClientSockfd, RESPONSE_404.c_str(), RESPONSE_404.size())) < 0) {
            // 写回错误信息失败
            logger->error("[GET] 获取文件{}信息失败:{}({})", filePath, __FILE__, __LINE__);
        }
        logger->error("[GET] 文件不存在:{}", filePath);
        return;
    } else if (!S_ISREG(st.st_mode) || !(S_IRUSR & st.st_mode)) {
        /* 
            通过着属性我们可以判断给定的文件是一个普通文件还是一个目录,连接等等.可以使用下面几个宏来判断.
                S_ISLNK(st_mode):是否是一个连接.

                S_ISREG是否是一个常规文件.

                S_ISDIR是否是一个目录

                S_ISCHR是否是一个字符设备.

                S_ISBLK是否是一个块设备

                S_ISFIFO是否是一个FIFO文件.

                S_ISSOCK是否是一个SOCKET文件. 

                S_IRUSR:所有者拥有读权限
        */
        while ((write(this->ClientSockfd, RESPONSE_403.c_str(), RESPONSE_403.size())) < 0) {
            logger->error("[GET] 获取文件{}信息失败:{}({}), 403", filePath, __FILE__, __LINE__);
        }
        logger->error("[GET] 文件不可读:{}", filePath);
        return;
    } else {
        // 判断文件是否修改
        struct tm tm{};
        localtime_r(&(st.st_mtime), &tm);  // st_mtime上次修改时间
        char timeBuf[40];
        strftime(timeBuf, 40,  "%a, %d %b %Y %H:%M:%S GMT", &tm); // 按照既定的格式转换时间
        std::string lastModified(timeBuf);
        transform(lastModified.begin(), lastModified.end(), lastModified.begin(), ::tolower);
        if(heads["if-modified-since"] == lastModified) {
            //如果文件未被修改
            if ((write(this->ClientSockfd, RESPONSE_304.c_str(), RESPONSE_304.size())) < 0) {
                logger->error("[GET] 发送 RESPONSE_304 失败:{} {}({})", filePath, __FILE__, __LINE__);
            }
            return;
        }

        responseHead = RESPONSE_200;
        responseHead += "Accept-Ranges: bytes\r\n";
        responseHead += "Content-Length:" + std::to_string(st.st_size) + "\r\n";
        responseHead += "Last-Modified: " + lastModified + "\r\n";
        if (heads["connection"] == "keep-alive") {
            this->KeepAlive = true;
            responseHead += "Connection:keep-alive\r\nKeep-Alive: timeout=" + std::to_string(KEEPALIVE_TIMEOUT) + "\r\n";
        } else {
            this->KeepAlive = false;
            responseHead += "Connection:close\r\n";
        }


        std::string type = this->messageBody["file_type"].asString();
        if (CONTENT_TYPE.find(type) != CONTENT_TYPE.end()) {
            responseHead += "Content-Type: " + CONTENT_TYPE.at(type) + "\r\n";
        }
        responseHead += "\r\n"; //注意是http报文头和数据之间还有个空行

        while ((write(this->ClientSockfd, responseHead.c_str(), responseHead.size())) <= 0) {
            if(errno == EINTR) continue;//执行过程中遇到了中断
            else {
                logger->error("[GET] 发送 RESPONSE_200 失败:{} {}({}) errno:{}", filePath, __FILE__, __LINE__, errno);
                return;
            }
        }
    }

    //打开文件
    if ((f = open(filePath.c_str(), O_RDONLY, 0)) < 0) { 
        logger->error("[GET] 打开文件{}错误:{}({})", filePath, __FILE__, __LINE__);
        if ((write(this->ClientSockfd, RESPONSE_500.c_str(), RESPONSE_500.size())) < 0) {
            logger->error("[GET] 发送 RESPONSE_500 失败:{} {}({})", filePath, __FILE__, __LINE__);
        }
        return;
    }

    //如果使用缓存且文件大小满足要求，则发送缓存文件内容
    if (this->UseRedis && st.st_size <= CACHE_FILE_MAX_SIZE) {
        try {
            std::string value = this->redis->getValue(filePath);
            if ((write(this->ClientSockfd, value.c_str(), value.size())) < 0) {
                logger->error("发送文件失败:{} {}({})", filePath, __FILE__, __LINE__);
                close(f);
                return;
            }
        } catch (int type) {
            // 如果键不存在 因为使用了缓存 如果大小没有超过规定缓存大小 则缓存
            if (type == REDIS_REPLY_NIL) { 
                std::string value;
                ssize_t n;

                // 读取文件
                while (true) {
                    n = read(f, FILE_BUF, N);
                    if (n == -1) {
                        close(f);
                        logger->error("[GET] 文件读取失败:{} {}({})", filePath, __FILE__, __LINE__);
                        return;
                    }
                    if (n == 0) { //EOF
                        redis->set(filePath, value);
                        break;
                    } else {
                        value += std::string(FILE_BUF);
                    }
                }


                if ((write(this->ClientSockfd, value.c_str(), value.size())) < 0) {
                    logger->error("[GET] 发送文件失败:{} {}({})", filePath, __FILE__, __LINE__);
                    close(f);
                    return;
                }
            } else {
                close(f);
                logger->error("[GET] redis读取失败{}:{} {}({})", type, filePath, __FILE__, __LINE__);
                return;
            }
        }
    } else {
        if ((sendfile(this->ClientSockfd, f, nullptr, st.st_size)) < 0) {
            logger->error("[GET] 发送文件失败:{} {}({})", filePath, __FILE__, __LINE__);
        }
        close(f);
    }
    //on = 0;
    //setsockopt(client_sockfd, SOL_TCP, TCP_CORK, &on, sizeof(on));
//    close(f);
}

void getProcessor::run() {
    logger->info("[GET] {} {}", llhttp_method_name(this->method), this->url);

    try {
        spdlog::info("请求的url是 {}", this->url);
        this->setMessageBody(this->url);
    }
    catch (std::exception& e) {
        logger->error("[GET] url解析出错:{}({})", __FILE__, __LINE__);
        throw e;
    }

    // 如果以'/'结尾的则说明是方法
    std::string fileName = this->messageBody["file_name"].asString();
    if (!fileName.empty()) {//是文件
        this->sendFile(fileName);
    } else {//不是获取静态文件，则执行相应的get方法
    
        throw std::runtime_error("[GET] 请求非文件");
    }
}
